/*
 * <one line to give the program's name and a brief idea of what it does.>
 * Copyright (C) 2019
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "player.hpp"

#include "arena.hpp"
#include "logger.h"
#include "shipai.hpp"
#include "shippart.hpp"
#include "dbmemory.hpp"
#include "dblmdb.hpp"
#include "dbsqlite.hpp"
#include "trophy.hpp"

#include "spdlog/spdlog.h"
#include "spdlog/fmt/ostr.h"

#include "nlohmann/json.hpp"
#include <fstream>
#include <thread>
#include <cmath>

std::mutex Player::m_Mutex;

bool Player::checkJSON(const nlohmann::json & data) {
	bool valid = true;

	if(data.is_object() && data.size() >= 18) {
		if(!data["testMilitaryChallenge"].is_number_unsigned()) {
			valid = false;
		}

		if(!data["baselineMilitary"].is_number_unsigned()) {
			valid = false;
		}

		if(!data["orbProgress"].is_number_unsigned()) {
			valid = false;
		}

		if(!data["fourthDrone"].is_string()) {
			valid = false;
		}

		if(!data["fifthDrone"].is_string()) {
			valid = false;
		}

		if(!data["testFourthDrone"].is_string()) {
			valid = false;
		}

		if(!data["testFifthDrone"].is_string()) {
			valid = false;
		}

		auto check = [&data, &valid](const char * key, uint32_t size) -> void {
			if(!data[key].is_array() || data[key].size() != size) {
				valid = false;
			} else {
				for(uint32_t i = 0; i < size; ++i) {
					if(!data[key][i].is_array() || data[key][i].size() != 1) {
						valid = false;
					}
				}
			}
		};

		check("bonuses", 9);
		check("baselineResources", 3);
		check("onhandResources", 2);
		check("ship1", 4);
		check("ship2", 4);
		check("ship3", 4);
		check("testShip1", 4);
		check("testShip2", 4);
		check("testShip3", 4);
		check("drones", 5);
		check("testDrones", 5);
	} else {
		valid = false;
	}

	return valid;
}

droneType Player::droneNameToType(const std::string name) {
	if(name == "Leech") {
		return droneType::LEECH;
	} else if(name == "Shield Absorb") {
		return droneType::SHIELD_ABSORBTION;
	} else if(name == "Regen") {
		return droneType::REGEN;
	}

	return droneType::CONTROLLER;
}

std::string Player::droneTypeToName(const droneType type) {
	std::string result;

	switch(type) {
		case droneType::CONTROLLER:
			result = "Controller";
			break;

		case droneType::DEAD:
			result = "Deadly Inc";
			break;

		case droneType::LEECH:
			result = "Leech";
			break;

		case droneType::REFLECTOR:
			result = "Reflection";
			break;

		case droneType::REGEN:
			result = "Regen";
			break;

		case droneType::SHIELD_ABSORBTION:
			result = "Shield Absorb";
			break;

		case droneType::SHIELD_PENETRATION:
			result = "Shield Pen";
			break;
	}

	return result;
}

bool Player::exportData(const char * file, const PlayerData * data) {
	std::ofstream out;
	out.open(file, std::ios_base::app);

	if(out.is_open() && !out.fail()) {
		// TODO convert state to json
		nlohmann::json e;
		e["testMilitaryChallenge"] = data->aiTest;
		e["baselineMilitary"] = data->aiBase;
		e["orbProgress"] = data->orbLevel;
		// bonuses
		e["bonuses"][0][0] = data->virtue;
		e["bonuses"][1][0] = data->orbStars;
		e["bonuses"][2][0] = data->guild - 1;
		e["bonuses"][3][0] = data->guildPassive - 1;
		e["bonuses"][4][0] = data->legendary;
		e["bonuses"][5][0] = data->ascDamage;
		e["bonuses"][6][0] = data->ascHp;
		e["bonuses"][7][0] = data->trophyHp;
		e["bonuses"][8][0] = data->trophyDamage;
		// resource
		e["baselineResources"][0][0] = data->resBase.imatter;
		e["baselineResources"][1][0] = data->resBase.pcore;
		e["baselineResources"][2][0] = data->resBase.ultium;
		e["onhandResources"][1][0] = data->resHand.imatter;
		e["onhandResources"][0][0] = data->resHand.ultium;

		//drones
		for(auto i = 0; i < 5; ++i) {
			e["drones"][i][0] = data->drones[i];
			e["testDrones"][i][0] = data->drones[i];
		}

// 		e["drones"][0][0] = data->drones[0];
// 		e["drones"][1][0] = data->drones[1];
// 		e["drones"][2][0] = data->drones[2];
// 		e["drones"][3][0] = data->drones[3];
// 		e["drones"][4][0] = data->drones[4];
// 		e["testDrones"][0][0] = data->drones[0];
// 		e["testDrones"][1][0] = data->drones[1];
// 		e["testDrones"][2][0] = data->drones[2];
// 		e["testDrones"][3][0] = data->drones[3];
// 		e["testDrones"][4][0] = data->drones[4];
		e["fourthDrone"] = droneTypeToName(data->droneTypes[3]);
		e["fifthDrone"] = droneTypeToName(data->droneTypes[4]);
		e["testFourthDrone"] = droneTypeToName(data->droneTypes[3]);
		e["testFifthDrone"] = droneTypeToName(data->droneTypes[4]);
		// ships
		e["ship1"][0][0] = data->buildBase.data[0];
		e["ship1"][1][0] = data->buildBase.data[1];
		e["ship1"][2][0] = data->buildBase.data[2];
		e["ship1"][3][0] = data->buildBase.data[3];

		e["ship2"][0][0] = data->buildBase.data[4];
		e["ship2"][1][0] = data->buildBase.data[5];
		e["ship2"][2][0] = data->buildBase.data[6];
		e["ship2"][3][0] = data->buildBase.data[7];

		e["ship3"][0][0] = data->buildBase.data[8];
		e["ship3"][1][0] = data->buildBase.data[9];
		e["ship3"][2][0] = data->buildBase.data[10];
		e["ship3"][3][0] = data->buildBase.data[11];

		e["testShip1"][0][0] = data->buildTest.data[0];
		e["testShip1"][1][0] = data->buildTest.data[1];
		e["testShip1"][2][0] = data->buildTest.data[2];
		e["testShip1"][3][0] = data->buildTest.data[3];

		e["testShip2"][0][0] = data->buildTest.data[4];
		e["testShip2"][1][0] = data->buildTest.data[5];
		e["testShip2"][2][0] = data->buildTest.data[6];
		e["testShip2"][3][0] = data->buildTest.data[7];

		e["testShip3"][0][0] = data->buildTest.data[8];
		e["testShip3"][1][0] = data->buildTest.data[9];
		e["testShip3"][2][0] = data->buildTest.data[10];
		e["testShip3"][3][0] = data->buildTest.data[11];

		out << e << std::endl;
		out.close();
		return true;
	}

	return false;
}

bool Player::importData(const char * file, PlayerData * data) {
	auto log = Logger::get("Player");
	std::ifstream in(file, std::ios_base::in);
	bool result = false;

	if(!in.fail()) {
		nlohmann::json import;
		in >> import;

		if(checkJSON(import)) {
			// AI challenge
			data->aiBase = import["baselineMilitary"];
			data->aiTest = import["testMilitaryChallenge"];

			// res base
			data->resBase.imatter = import["baselineResources"][0][0];
			data->resBase.pcore = import["baselineResources"][1][0];
			data->resBase.ultium = import["baselineResources"][2][0];
			// res on hand
			data->resHand.imatter = import["onhandResources"][1][0];
			data->resHand.ultium = import["onhandResources"][0][0];
			// res for upgrades
			data->resUpgrade = data->resHand;
			data->resUpgrade += ShipAI::rewardTotal(data->aiTest - 1);
			data->resUpgrade -= ShipAI::rewardTotal(data->aiBase);

			// orb
			data->virtue = import["bonuses"][0][0];
			data->orbStars = import["bonuses"][1][0];
			data->orbLevel = import["orbProgress"];
			data->orbValue = 1 + (0.02 * std::pow(data->orbStars, 2) + (double)data->orbLevel / 100 * (0.08 * data->orbStars + 0.04) * 0.4) * (1 + data->virtue  * 0.005);

			// guild
			data->guild = 1 + (double)import["bonuses"][2][0];
			data->guildPassive = 1 + (double)import["bonuses"][3][0];

			// upgrades
			data->legendary = import["bonuses"][4][0];
			data->ascDamage = import["bonuses"][5][0];
			data->ascHp = import["bonuses"][6][0];
			data->trophyHp = import["bonuses"][7][0];
			data->trophyDamage = import["bonuses"][8][0];

			// drones
			// reflection drone
			data->drones[0] = import["drones"][0][0];
			data->drones[1] = import["drones"][1][0];
			data->drones[2] = import["drones"][2][0];
			data->drones[3] = import["drones"][3][0];
			data->drones[4] = import["drones"][4][0];
			data->droneTypes[0] = droneType::REFLECTOR;
			data->droneTypes[1] = droneType::SHIELD_PENETRATION;
			data->droneTypes[2] = droneType::DEAD;
			data->droneTypes[3] = droneNameToType(import["fourthDrone"]);
			data->droneTypes[4] = droneNameToType(import["fifthDrone"]);

			// left ship
			data->buildBase.data[0] = import["ship1"][0][0];
			data->buildBase.data[1] = import["ship1"][1][0];
			data->buildBase.data[2] = import["ship1"][2][0];
			data->buildBase.data[3] = import["ship1"][3][0];
			// middle ship
			data->buildBase.data[4] = import["ship2"][0][0];
			data->buildBase.data[5] = import["ship2"][1][0];
			data->buildBase.data[6] = import["ship2"][2][0];
			data->buildBase.data[7] = import["ship2"][3][0];
			// right ship
			data->buildBase.data[8] = import["ship3"][0][0];
			data->buildBase.data[9] = import["ship3"][1][0];
			data->buildBase.data[10] = import["ship3"][2][0];
			data->buildBase.data[11] = import["ship3"][3][0];

			// copy to testShips & testDrones
			data->buildTest.data[0] = import["testShip1"][0][0];
			data->buildTest.data[1] = import["testShip1"][1][0];
			data->buildTest.data[2] = import["testShip1"][2][0];
			data->buildTest.data[3] = import["testShip1"][3][0];
			data->buildTest.data[4] = import["testShip2"][0][0];
			data->buildTest.data[5] = import["testShip2"][1][0];
			data->buildTest.data[6] = import["testShip2"][2][0];
			data->buildTest.data[7] = import["testShip2"][3][0];
			data->buildTest.data[8] = import["testShip3"][0][0];
			data->buildTest.data[9] = import["testShip3"][1][0];
			data->buildTest.data[10] = import["testShip3"][2][0];
			data->buildTest.data[11] = import["testShip3"][3][0];
			result = true;
		} else {
			log->error("Invalid json object from file {}", file);
		}

		in.close();
	} else {
		log->error("Cannot open file '{}' for import", file);
	}

	return result;
}

void Player::createGraph(DataBase * out, PlayerData data) {
	out->erase();

	DBMemory queue(sizeof(PathData));
	PathData begin;
	std::memcpy(begin.ship.data, data.buildBase.data, sizeof(PlayerBuild::data));
	begin.ai[0] = data.aiBase;
	begin.ai[1] = data.aiBase;
	begin.ultium = data.resHand.ultium;
	begin.imatter = data.resHand.imatter;
	queue.put(&begin, sizeof(begin));

	DBSQLite sql("test.dbs");
	sql.erase();
	DBLMDB lmdb("lmdb/0");
	lmdb.erase();

	// sql
// 	graphWorker(&queue, &sql, data);
	// lmdb
	graphWorker(&queue, &lmdb, data);
	sql.moveFromLMDB(&lmdb);

	const std::string finalDBName = "path.dbs";
	DBSQLite::prepareDB(finalDBName);
	sql.copyDataTo(finalDBName);

	DBSQLite path(finalDBName, false);
	path.findPathCommon(out);
}

void Player::createGraphMT(DataBase * out, PlayerData data) {
	std::shared_ptr<spdlog::logger> log = Logger::get("Player");

	out->erase();

	// setup data for N workers
	std::size_t sectors = std::thread::hardware_concurrency();
// 	sectors = 4;
	std::vector<PathWorketMT> workersData;

	// testing lmdb;
// 	for(auto i = 0; i < sectors; ++i) {
// 		workersData.push_back({
// 			new DBMemory(sizeof(PathData)),
// 			new DBLMDB("lmdb/" + std::to_string(i)),
// 			new std::condition_variable,
// 			new std::mutex
// 		});
// 	}

	// testing sqlite
	for(auto i = 0; i < sectors; ++i) {
		workersData.push_back({
			new DBMemory(sizeof(PathData)),
// 			new DBSQLite("path_" + std::to_string(i) + ".dbs"),
			new DBSQLite(":memory:"),
			new std::condition_variable,
			new std::mutex
		});
	}

	for(auto & i : workersData) {
		i.out->erase();
	}

	// start point
	PathData start;
	std::memcpy(start.ship.data, data.buildBase.data, sizeof(PlayerBuild::data));
	start.ai[0] = data.aiBase;
	start.ai[1] = data.aiBase;
	start.ultium = data.resHand.ultium;
	start.imatter = data.resHand.imatter;
	// find first sector!
	std::cout << "Start sector id : " << sectorID(&start.ship, sectors) << std::endl;
	log->info("Start sector: {}. Total sectors: {}", sectorID(&start.ship, sectors), sectors);
	workersData[sectorID(&start.ship, sectors)].queue->put(&start, sizeof(start));

	// prepare database
	const std::string finalDBName = "path.dbs";
	DBSQLite::prepareDB(finalDBName);

	// предельный уровень, после нахождения которого тормозим нафиг
	std::atomic<bool> stop(false);

	std::vector<std::thread> thrs;

	for(auto i = 0; i < workersData.size(); ++i) {
		thrs.push_back(std::thread(Player::graphWorkerMT,
		                           &workersData,
		                           data,
		                           std::ref(stop),
		                           i,
		                           workersData.size()
		                          ));
	}

	for(auto & i : thrs) {
		i.join();
	}

	// delete and move data
	for(auto & i : workersData) {
		if(dynamic_cast<DBLMDB *>(i.out) != nullptr) {
			// move to main sqlite
			DBSQLite sqldb(finalDBName, false);
			sqldb.moveFromLMDB(dynamic_cast<DBLMDB *>(i.out));
		} else if(dynamic_cast<DBSQLite *>(i.out) != nullptr) {
			DBSQLite * db = dynamic_cast<DBSQLite *>(i.out);
			db->copyDataTo(finalDBName);
		}

		delete i.ready;
		delete i.queue;
		delete i.out;
		delete i.mutex;
	}

	DBSQLite path(finalDBName, false);
	path.findPathCommon(out);
}

uint16_t Player::sectorID(const PlayerBuild * data, const uint16_t maxID) {
	uint32_t sum = 0;

	for(auto i : data->data)
		sum += i;

	return sum % maxID;
}

void Player::graphWorkerMT(std::vector<PathWorketMT> * workerData,
                           PlayerData data,
                           std::atomic<bool> & stop,
                           uint16_t workerID,
                           uint16_t maxID
                          ) {

	auto partSummary = [] (PlayerBuild * data) -> uint32_t {
		uint32_t sum = 0;
		for( const auto &i : data->data ){
			sum += i;
		}
		return sum;
	};
	// structs for work
	PathData currentBuild;
	resource onHand;
	uint32_t amount = 0;
	uint16_t nextWorkerID = workerID + 1;
	uint16_t maxAILevel = data.aiTest;
	uint32_t sumMax = 0;
	uint32_t sumCurrent;

	if(nextWorkerID >= maxID)
		nextWorkerID = 0;

	auto log = Logger::get("Player");

	// set limits!
	PlayerBuild limits;
	std::memcpy(limits.data, data.buildTest.data, sizeof(PlayerBuild::data));
	Arena fight(&data);
	uint16_t reachAI = 0;
	std::unique_lock<std::mutex> lock(*(*workerData)[workerID].mutex);

	while(!stop.load()) {

		while((*workerData)[workerID].queue->empty() && !stop.load()) {
			if((*workerData)[workerID].ready->wait_for(lock, std::chrono::milliseconds(50)) == std::cv_status::timeout) {
				// check if have unfinished jobs in queue
				bool mustStop = true;
				for( auto &i : *workerData ){
					if( i.queue->size() > 0 ){
						mustStop = false;
						break;
					}
				}
				if( mustStop ){
					stop.store(true);
					log->info("Stop search!");
					for( auto i = 0; i < (*workerData).size(); ++i ){
						log->info("worker #{}: not proccessed: {}", i,  (*workerData)[workerID].queue->size());
					}
					break;
				}
			}
		}

		// check if we must stop
		if(stop.load()) {
			log->info("Stop by signal");
			break;
		} else if((*workerData)[workerID].queue->get(&currentBuild, sizeof(currentBuild))) {
			amount ++;

			// clean database every 100k?
			sumCurrent = partSummary(&currentBuild.ship);
			if( sumCurrent > sumMax ){
				sumMax = sumCurrent;
				if( DBSQLite * sqldb = dynamic_cast<DBSQLite *>((*workerData)[workerID].out) ){
					log->info("Worker #{}: proccess: {} partial remove: {}", workerID, amount, sqldb->removeZeroWinsPartial());
					std::lock_guard<std::mutex> lock(m_Mutex);
					log->info("Moved {} records to {} database", sqldb->moveUnuseDataTo("path.dbs"), "path.dbs");
				}
			}

			std::memcpy(data.buildTest.data, currentBuild.ship.data, sizeof(currentBuild.ship.data));
			// NOTE do not rewrite baseAI level. trophy points will not calculated right!
// 			data.aiBase = currentBuild.ai[1];
			data.aiTest = currentBuild.ai[1] + 1;

			// resources to upgrading
			onHand.pcore = 0;
			onHand.ultium = currentBuild.ultium;
			onHand.imatter = currentBuild.imatter;

			while(true) {

				if(checkWin(&fight, &data)) {
					data.aiTest++;
				} else {
					onHand += ShipAI::rewardTotal(data.aiTest - 1);
					onHand -= ShipAI::rewardTotal(currentBuild.ai[1]);

					// write max and min ai lvls
					currentBuild.ai[0] = currentBuild.ai[1];
					currentBuild.ai[1] = data.aiTest - 1;

					// some check TODO remove if all ok
					if(data.aiTest - 1 > reachAI) {
						reachAI = data.aiTest - 1;
						currentBuild.ultium = onHand.ultium;
						currentBuild.imatter = onHand.imatter;
						DBMemory testdb(sizeof(TrophyUpgrade));
						uint32_t trophyPoints = Trophy::pointsTest(&data, true);
						Trophy::variants(&testdb, &data);
						log->debug("worker {} reach level {}, trophy {} : {}", workerID, reachAI, trophyPoints, currentBuild);
					}

					// update current build!
					if((*workerData)[workerID].out->updateBuild(&currentBuild)) {
						// TODO ALERT!
					} else {
						log->info("failed update build");
					}

					// must we stop search?
					if(data.aiTest - 1 >= maxAILevel) {
						stop.store(true);

						// signal to all worker
						for(auto & i : *workerData) {
							i.ready->notify_all();
						}

						break;
					}

					// store new builds
					std::vector<PathData> futureBuilds;

					for(uint16_t i = 0; i < upgradeCount; ++i) {
						resource upPrice = ShipPart::upgradePrice(currentBuild.ship.data[i] + 1);

						if(currentBuild.ship.data[i] < limits.data[i] && onHand >= upPrice) {
							currentBuild.ship.data[i]++;
							onHand -= upPrice;
							currentBuild.ultium = onHand.ultium;
							currentBuild.imatter = onHand.imatter;
							// store to check
							futureBuilds.push_back(currentBuild);
							// rollback

							currentBuild.ship.data[i]--;
							onHand += upPrice;
						}
					}

// size_t oldc = (*workerData)[nextWorkerID].out->size();
					// write new build to queue and database and start other queue
					if((*workerData)[nextWorkerID].out->addBuilds(&futureBuilds, nullptr, (*workerData)[nextWorkerID].queue) > 0) {
						(*workerData)[nextWorkerID].ready->notify_all();
					}

// std::cout << "#" << amount << " " << oldc << " => " << (*workerData)[nextWorkerID].out->size() << std::endl;
					break;
				}
			}
		}

	}

	log->info("Worker #{}: process {} builds, store in database: {}", workerID, amount, (*workerData)[nextWorkerID].out->size());

	// NOTE for speed up search can drop rows where min = max
	if( dynamic_cast<DBSQLite *>((*workerData)[nextWorkerID].out) != nullptr ){
		log->info("Worker #{}: Remove {} unused rows", workerID, dynamic_cast<DBSQLite *>((*workerData)[nextWorkerID].out)->removeZeroWins());
	} else if( dynamic_cast<DBLMDB *>((*workerData)[nextWorkerID].out) != nullptr ){
		// TODO
	}
}

void Player::graphWorker(DataBase * queue, DataBase * out, PlayerData data) {
	auto log = Logger::get("Player");
	// our structs for work
	PathData currentBuild;
	resource onHand;
	uint32_t amount = 0;
	Arena fight(&data);

	// set limits!
	PlayerBuild limits;
	std::memcpy(limits.data, data.buildTest.data, sizeof(PlayerBuild::data));
	uint16_t stopAILevel = data.aiTest;
	bool stopWork = false;
	uint16_t reachAI = 0;

	while(queue->get(&currentBuild, sizeof(currentBuild)) && !stopWork) {
		amount++;

		// set up
		std::memcpy(data.buildTest.data, currentBuild.ship.data, sizeof(currentBuild.ship.data));
		// NOTE do not rewrite baseAI level. trophy points will not calculated right!
// 		data.aiBase = currentBuild.ai[1];
		data.aiTest = currentBuild.ai[1] + 1;

		// how many resources we can spend to upgrade
		onHand.pcore = 0;
		onHand.ultium = currentBuild.ultium;
		onHand.imatter = currentBuild.imatter;

		uint32_t lastMaxAI;

		while(true) {
			lastMaxAI = data.aiTest;

			if(checkWin(&fight, &data)) {
				data.aiTest++;
			}

			if(lastMaxAI == data.aiTest) {

				// check if must stop
				if(data.aiTest - 1 >= stopAILevel) {
					stopWork = true;
					log->info("Reach {} level, stop working...", data.aiTest - 1);
				}

				// NOTE we don't win aiTest level, don't forget -1!
				onHand += ShipAI::rewardTotal(data.aiTest - 1);
				onHand -= ShipAI::rewardTotal(currentBuild.ai[1]);

				// write to database how many lvl we done
				currentBuild.ai[0] = currentBuild.ai[1];
				currentBuild.ai[1] = data.aiTest - 1;

				// some checks TODO remove if all ok
				if(data.aiTest - 1 > reachAI) {
					reachAI = data.aiTest - 1;
					currentBuild.ultium = onHand.ultium;
					currentBuild.imatter = onHand.imatter;
					DBMemory testdb(sizeof(TrophyUpgrade));
					uint32_t trophyPoints = Trophy::pointsTest(&data, true);
					Trophy::variants(&testdb, &data);
					log->debug("reach level {}, trophy {}, db {} : {}", reachAI, trophyPoints, testdb.size(), currentBuild);
				}

				std::vector<PathData> futureBuilds;

				// create new builds if possible
				for(uint32_t i = 0; i < tableSize::upgradeCount; ++i) {
					// NOTE we must have enough resources to buy upgrade and don't forget about limits!
					resource upPrice = ShipPart::upgradePrice(currentBuild.ship.data[i] + 1);

					if(currentBuild.ship.data[i] < limits.data[i] && onHand >= upPrice) {
						currentBuild.ship.data[i]++;
						onHand -= upPrice;
						currentBuild.ultium = onHand.ultium;
						currentBuild.imatter = onHand.imatter;
						// store to check
						futureBuilds.push_back(currentBuild);
						// rollback

						currentBuild.ship.data[i]--;
						onHand += upPrice;
					}
				}

				out->addBuilds(&futureBuilds, &currentBuild, queue);
				break;
			}
		}
	}

	log->info("Process {} builds, store in database: {}", amount, out->size());

	// NOTE for speed up search can drop rows where min = max
	if( dynamic_cast<DBSQLite *>(out) != nullptr ){
		log->info("Remove {} unused rows", dynamic_cast<DBSQLite *>(out)->removeZeroWins());
	} else if( dynamic_cast<DBSQLite *>(out) != nullptr ){
		// TODO
	}
}

void Player::finalizePath(DataBase * db, PlayerData data) {
	uint16_t maxAILevel = tableSize::ai;
	TrophyUpgrade trophyUp;
	TrophyUpgrade trophyLast;
	PathData build;
	Arena fight(&data);
	DBMemory trophyBD(sizeof(trophyUp));

	while(db->get(&build, sizeof(build))) {
		// copy data to data
		std::memcpy(data.buildTest.data, build.ship.data, sizeof(build.ship.data));

		for(uint16_t i = build.ai[1]; i > build.ai[0]; --i) {
			// skip already done
			if(i >= maxAILevel) {
				continue;
			}

			// do not use checkWin here!
			data.aiTest = i;
			trophyBD.erase();
			Trophy::variants(&trophyBD, &data);
			std::pair<TrophyUpgrade, uint8_t> trophyBest(TrophyUpgrade(), tableSize::trophy * 2);

			while(trophyBD.get(&trophyUp, sizeof(trophyUp))) {
				data.trophyDamage = trophyUp.damage;
				data.trophyHp = trophyUp.hp;
				fight.prepare();

				if(fight.run()) {
					// its end of path, dont know trophy levels yet. set it!
					if(trophyLast.damage == 0 && trophyLast.hp == 0) {
						trophyLast.hp = data.trophyHp;
						trophyLast.damage = data.trophyDamage;
					}

					// check the diff and find the best TODO use abs?
					uint8_t diff = trophyUp.damage > trophyLast.damage ? trophyUp.damage - trophyLast.damage : trophyLast.damage - trophyUp.damage;
					diff += trophyUp.hp > trophyLast.hp ? trophyUp.hp - trophyLast.hp : trophyLast.hp - trophyUp.hp;

					if(diff < trophyBest.second) {
						trophyBest.first.damage = trophyUp.damage;
						trophyBest.first.hp = trophyUp.hp;
						trophyBest.second = diff;
					}
				}
			}

			data.trophyHp = trophyBest.first.hp;
			data.trophyDamage = trophyBest.first.damage;

			trophyLast.damage = trophyBest.first.damage;
			trophyLast.hp = trophyBest.first.hp;
			exportData("path.txt", &data);
			maxAILevel = i;
		}
	}
}

bool Player::checkWin(Arena * fight, PlayerData * data) {
	uint8_t trophyUP[2];
	DBMemory trophyDB(sizeof(trophyUP));

	Trophy::variants(&trophyDB, data);

	while(trophyDB.get(trophyUP, sizeof(trophyUP))) {
		// set up
		data->trophyDamage = trophyUP[1];
		data->trophyHp = trophyUP[0];

		// fight!
		fight->prepare();

		if(fight->run()) {
			return true;
		}
	}

	return false;
}

